﻿using System;
using System.IO;

namespace LumenLiquid.Database
{
    internal static class XNB
    {
        #region ASF Format Constants

        private const Int64 AsfMinstreamsize = 0x78;
        private static readonly Guid AsfHeader = new Guid("75B22630-668E-11CF-A6D9-00AA0062CE6C");
        private static readonly Guid AsfVideo = new Guid("BC19EFC0-5B4D-11CF-A8FD-00805F5C442B");
        private static Guid _asfFileinfo = new Guid("8CABDCA1-A947-11CF-8EE4-00C00C205365");
        private static Guid _asfStreaminfo = new Guid("B7DC0791-A9B7-11CF-8EE6-00C00C205365");
        private static Guid _asfExtendedstreaminfo = new Guid("14E6A5CB-C672-4332-8399-A96952065B5A");
        private static Int64 _asfMinexstreamsize = 0x88;

        #endregion

        public static bool Wmv2Xnb(string filepath, Stream wmv, Guid gameGUID)
        {
            string path = System.IO.Path.GetDirectoryName(filepath);
            string wmvFile = path + "\\" + System.IO.Path.GetFileNameWithoutExtension(filepath) + ".wmv";
            string xnbFile = path + "\\" + System.IO.Path.GetFileNameWithoutExtension(filepath) + ".xnb";

            //Write wmv file
            var wmvStream = new FileStream(wmvFile, FileMode.Create);
            wmv.CopyTo(wmvStream);
            wmvStream.Close();
            wmv.Seek(0, SeekOrigin.Begin);

            #region Generate XNB

            var xnbStream = new FileStream(xnbFile, FileMode.Create);
            var wmvBin = new BinaryReader(wmv);
            var xnbBin = new BinaryWriter(xnbStream);

            byte[] header = wmvBin.ReadBytes(16);
            if (new Guid(header) != AsfHeader) //Header that indicates the wmv (asf) file
            {
                xnbBin.Close();
                xnbStream.Close();

                File.Delete(wmvFile);
                File.Delete(xnbFile);
                return false;
            }

            /*Read header:
			 * Int64
			 * Int32
			 * Byte
			 * Byte
			*/

            wmvBin.ReadInt64(); //Object Size
            wmvBin.ReadInt32(); //Number of Header Objects
            wmvBin.ReadByte(); //Reserved1 = 0x01
            wmvBin.ReadByte(); //Reserved2 = 0x02

            //Find fileinfo or streaminfo, because information has no order

            #region Find Position in stream

            bool fail = false, file = false, stream = false, exstream = false;
            long fileStart = 0, streamStart = 0, extendedStreamStart = 0;
            while (true)
            {
                byte result;

                try
                {
                    result = wmvBin.ReadByte();
                }
                catch
                {
                    xnbStream.Close();
                    return false;
                }

                if (result == _asfFileinfo.ToByteArray()[0]) //Start of FileInfo GUID found, check whole GUID
                {
                    var i = 0;
                    for (; i < 16; i++)
                    {
                        if (result != _asfFileinfo.ToByteArray()[i])
                        {
                            fail = true;
                            break;
                        }

                        result = wmvBin.ReadByte();
                    }

                    if (!fail)
                    {
                        fileStart = wmvBin.BaseStream.Position - 1;
                        //Jump one back because we read a byte too much when successing GUID

                        file = true;
                    }
                    wmv.Seek(-(i - 1), SeekOrigin.Current);
                }

                fail = false;

                if (result == _asfStreaminfo.ToByteArray()[0]) //Start of StreamInfo GUID found, check whole GUID
                {
                    var i = 0;
                    for (; i < 16; i++)
                    {
                        if (result != _asfStreaminfo.ToByteArray()[i])
                        {
                            fail = true;
                            break;
                        }

                        result = wmvBin.ReadByte();
                    }

                    if (!fail)
                    {
                        streamStart = wmvBin.BaseStream.Position - 1; //Jump one back, see up
                        wmv.Seek(streamStart, SeekOrigin.Begin);
                        //Also check minimum Object Size because there can be multiple streamGUIDs.
                        var objectSize = wmvBin.ReadInt64();
                        var guid = new Guid(wmvBin.ReadBytes(16));
                        if (objectSize >= AsfMinstreamsize && guid == AsfVideo)
                        {
                            stream = true;
                        }
                        wmv.Seek(streamStart, SeekOrigin.Begin); //Reset pointer before ObjectSize
                    }
                    wmv.Seek(-(i - 1), SeekOrigin.Current);
                }

                fail = false;

                if (result == _asfExtendedstreaminfo.ToByteArray()[0])
                    //Start of ExtendedStreamInfo GUID found, check whole GUID
                {
                    var i = 0;
                    for (; i < 16; i++)
                    {
                        if (result != _asfExtendedstreaminfo.ToByteArray()[i])
                        {
                            fail = true;
                            break;
                        }

                        result = wmvBin.ReadByte();
                    }

                    if (!fail)
                    {
                        extendedStreamStart = wmvBin.BaseStream.Position - 1; //job,s.u.

                        exstream = true;


                        wmv.Seek(extendedStreamStart, SeekOrigin.Begin);
                    }
                    wmv.Seek(-(i - 1), SeekOrigin.Current);
                }

                fail = false;
                if (file && stream && exstream)
                    break;
            }

            #endregion

            #region Duration

            wmv.Seek(fileStart, SeekOrigin.Begin);
            /*Useless information
			 * Int64
			 * Guid (2xInt64)
			 * Int64
			 * Int64
			 * Int64
			 */
            wmvBin.ReadInt64(); //Object size
            wmvBin.ReadBytes(16); //File GUID
            wmvBin.ReadInt64(); //File Size
            wmvBin.ReadInt64(); //Creation date (in ns since 01.01.1601)
            wmvBin.ReadInt64(); //Data Packet Count
            var wmvDuration = (int) (wmvBin.ReadInt64()/10000);

            #endregion

            #region Video Size

            wmv.Seek(streamStart, SeekOrigin.Begin);
            /*Useless information 
			 * Int64
			 * Guid (2xInt64) -> Check mediatype
			 * Guid (2xInt64)
			 * Int64
			 * Int32
			 * Int32
			 * Int16
			 * Int32
			 */
            wmvBin.ReadInt64(); //Size of Object >= 0x78		
            var videoBin = new Guid(wmvBin.ReadBytes(16));
            if (videoBin != AsfVideo) //This file is not a video file
                return false;

            wmvBin.ReadBytes(16); //Error Correction Type GUID
            wmvBin.ReadInt64(); //Time Offset
            wmvBin.ReadInt32(); //Type-Specific data length
            wmvBin.ReadInt32(); //Error correction data length
            wmvBin.ReadInt16(); //Flags
            wmvBin.ReadInt32(); //Reserved = 0x00

            //Can only be callen if the file is a video file
            int wmvSizeX = wmvBin.ReadInt32();
            int wmvSizeY = wmvBin.ReadInt32();

            #endregion

            #region framerate

            wmv.Seek(extendedStreamStart, SeekOrigin.Begin);
            /*Useless information
			 * Int64
			 * Int64
			 * Int64
			 * Int32
			 * Int32
			 * Int32
			 * Int32
			 * Int32
			 * Int32
			 * Int32
			 * Int32
			 * Int16
			 * Int16
			 */
            wmvBin.ReadInt64(); //Object Size
            wmvBin.ReadInt64(); //Start Time
            wmvBin.ReadInt64(); //End Time
            wmvBin.ReadInt32(); //Data Bitrate
            wmvBin.ReadInt32(); //Buffer Size
            wmvBin.ReadInt32(); //Initial Buffer Fullness
            wmvBin.ReadInt32(); //Alternate Data Bitrate
            wmvBin.ReadInt32(); //Alternate Buffer Size
            wmvBin.ReadInt32(); //Alternate Initial Buffer Fullness
            wmvBin.ReadInt32(); //Maximum Object Size
            wmvBin.ReadInt32(); //Flags
            wmvBin.ReadInt16(); //Stream Number
            wmvBin.ReadInt16(); //Stream Language ID Index

            var wmvFramerate = (Single) (1/((double) wmvBin.ReadInt64()/10000000));
            //Convert unit (100ns) to normal unit (1ms) and get the frequency

            #endregion

            #region writeXNB

            xnbBin.Write(new[] {(byte) 'X', (byte) 'N', (byte) 'B'}); //Identifier for XNB files
            xnbBin.Write((byte) 'w'); //Platform w->windows
            xnbBin.Write((byte) 0x05); //Version 0x05->XNA 4.0
            xnbBin.Write((byte) 0x01); //Uncompressed and HiDef
            var sizePosition = xnbBin.BaseStream.Position;
            xnbBin.Write(0); //Size of file, insert afterwards
            xnbBin.Write(new byte[]
                             {
                                 0x04, 0x8D, 0x01, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x58,
                                 0x6E, 0x61, 0x2E, 0x46, 0x72, 0x61, 0x6D, 0x65, 0x77, 0x6F, 0x72, 0x6B, 0x2E, 0x43,
                                 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2E, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x52, 0x65,
                                 0x61, 0x64, 0x65, 0x72, 0x2C, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66,
                                 0x74, 0x2E, 0x58, 0x6E, 0x61, 0x2E, 0x46, 0x72, 0x61, 0x6D, 0x65, 0x77, 0x6F, 0x72,
                                 0x6B, 0x2E, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x2C, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69,
                                 0x6F, 0x6E, 0x3D, 0x34, 0x2E, 0x30, 0x2E, 0x30, 0x2E, 0x30, 0x2C, 0x20, 0x43, 0x75,
                                 0x6C, 0x74, 0x75, 0x72, 0x65, 0x3D, 0x6E, 0x65, 0x75, 0x74, 0x72, 0x61, 0x6C, 0x2C,
                                 0x20, 0x50, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x4B, 0x65, 0x79, 0x54, 0x6F, 0x6B, 0x65,
                                 0x6E, 0x3D, 0x38, 0x34, 0x32, 0x63, 0x66, 0x38, 0x62, 0x65, 0x31, 0x64, 0x65, 0x35,
                                 0x30, 0x35, 0x35, 0x33, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x4D, 0x69, 0x63, 0x72, 0x6F,
                                 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x58, 0x6E, 0x61, 0x2E, 0x46, 0x72, 0x61, 0x6D, 0x65,
                                 0x77, 0x6F, 0x72, 0x6B, 0x2E, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2E, 0x53,
                                 0x74, 0x72, 0x69, 0x6E, 0x67, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x00, 0x00, 0x00,
                                 0x00, 0x2B, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x58, 0x6E,
                                 0x61, 0x2E, 0x46, 0x72, 0x61, 0x6D, 0x65, 0x77, 0x6F, 0x72, 0x6B, 0x2E, 0x43, 0x6F,
                                 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2E, 0x49, 0x6E, 0x74, 0x33, 0x32, 0x52, 0x65, 0x61,
                                 0x64, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73,
                                 0x6F, 0x66, 0x74, 0x2E, 0x58, 0x6E, 0x61, 0x2E, 0x46, 0x72, 0x61, 0x6D, 0x65, 0x77,
                                 0x6F, 0x72, 0x6B, 0x2E, 0x43, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x2E, 0x53, 0x69,
                                 0x6E, 0x67, 0x6C, 0x65, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00,
                                 0x00, 0x01
                             }); //Write needed importers and processors
            xnbBin.Write((byte) 0x02); //now a string
            xnbBin.Write(System.IO.Path.GetFileName(wmvFile)); //Write Filename
            xnbBin.Write((byte) 0x03); //Int32
            xnbBin.Write(wmvDuration); //Write Video duration
            xnbBin.Write((byte) 0x03); //Int32
            xnbBin.Write(wmvSizeX); //Write width
            xnbBin.Write((byte) 0x03); //Int32
            xnbBin.Write(wmvSizeY); //Write Height
            xnbBin.Write((byte) 0x04); //Single
            xnbBin.Write(wmvFramerate); //Write framerate
            xnbBin.Write((byte) 0x03); //Int32
            xnbBin.Write(0); //Type=music

            xnbBin.BaseStream.Seek(sizePosition, SeekOrigin.Begin);
            xnbBin.Write((int) xnbBin.BaseStream.Length); //Write size to file
            xnbBin.Close();
            xnbStream.Close();

            #endregion

            #endregion

            return true;
        }
    }
}